﻿using System;
using System.Xml.Serialization;

namespace MathService.Vectors
{
    /// <summary>Трёхмерный вектор</summary>
    [System.ComponentModel.TypeConverter(typeof(Vector3DConverter))]
    public partial struct Vector3D : IEquatable<Vector3D>, ICloneable<Vector3D>
    {
        /* -------------------------------------------------------------------------------------------- */

        [System.Diagnostics.DebuggerStepThrough]
        public static Vector3D RThettaPhi(double R, double Thetta, double Phi)
        {
            return new Vector3D(R, new SpaceAngle(Thetta, Phi));
        }

        [System.Diagnostics.DebuggerStepThrough]
        public static Vector3D XYZ(double X, double Y, double Z)
        {
            return new Vector3D(X, Y, Z);
        }

        public static Vector3D Random(double min = -100, double max = 100)
        {
            var rnd = new Random();
            Func<double> RND = () => Math.Abs(max - min) * (rnd.NextDouble() - .5) + (max + min) * .5;
            return new Vector3D(RND(), RND(), RND());
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Единичный базисный вектор</summary>
        public static Vector3D BasysUnitVector
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return new Vector3D(1, 1, 1); }
        }
        /// <summary>Базисный вектор i</summary>
        public static Vector3D BasysVector_i
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return new Vector3D(1, 0, 0); }
        }
        /// <summary>Базисный вектор j</summary>
        public static Vector3D BasysVector_j
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return new Vector3D(0, 1, 0); }
        }
        /// <summary>Базисный вектор k</summary>
        public static Vector3D BasysVector_k
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return new Vector3D(0, 0, 1); }
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Длина по оси X</summary>
        private double f_X;

        /// <summary>Длина по оси Y</summary>
        private double f_Y;

        /// <summary>Длина по оси Z</summary>
        private double f_Z;

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Длина по оси X</summary>
        [XmlAttribute]
        public double X
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_X; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_X = value; }
        }

        /// <summary>Длина по оси Y</summary>
        [XmlAttribute]
        public double Y
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_Y; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_Y = value; }
        }

        /// <summary>Длина по оси Z</summary>
        [XmlAttribute]
        public double Z
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return f_Z; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_Z = value; }
        }

        /// <summary>Длина вектора</summary>
        [XmlIgnore]
        public double R
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return Math.Sqrt(f_X * f_X + f_Y * f_Y + f_Z * f_Z); }
            //[System.Diagnostics.DebuggerStepThrough]
            set { SetValue(value, Angle); }
        }

        /// <summary>Угол проекции в плоскости XOY</summary>
        public double AngleXOY
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get
            {
                return Math.Abs(f_X) < double.Epsilon
                    ? (Math.Abs(f_Y) < double.Epsilon       // X == 0
                        ? 0                                 //  Y == 0 => 0
                        : Math.Sign(f_Y) * Consts.pi05)     //  Y != 0 => pi/2 * sign(Y)
                    : (Math.Abs(f_Y) < double.Epsilon       // X != 0
                        ? (Math.Sign(f_X) > 0
                            ? 0
                            : Consts.pi)
                        : Math.Atan2(f_Y, f_X));            //  Y != 0 => atan(Y/X)
            }
        }

        /// <summary>Угол проекции в плоскости XOZ</summary>
        public double AngleXOZ
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get
            {
                return Math.Abs(f_X) < double.Epsilon
                    ? (Math.Abs(f_Z) < double.Epsilon       // X == 0
                        ? 0                                 //  Z == 0 => 0
                        : Math.Sign(f_Z) * Consts.pi05)     //  Z != 0 => pi/2 * sign(Z)
                    : (Math.Abs(f_Z) < double.Epsilon       // X != 0
                        ? (Math.Sign(f_X) > 0
                            ? 0
                            : Consts.pi)
                        : Math.Atan2(f_Z, f_X));            //  Z != 0 => atan(Y/X)
            }
        }

        /// <summary>Угол проекции в плоскости YOZ</summary>
        public double AngleYOZ
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get
            {
                return Math.Abs(f_Y) < double.Epsilon
                    ? (Math.Abs(f_Z) < double.Epsilon       // Y == 0
                        ? 0                                 //  Z == 0 => 0
                        : Math.Sign(f_Z) * Consts.pi05)     //  Z != 0 => pi/2 * sign(Y)
                    : (Math.Abs(f_Z) < double.Epsilon       // Y != 0
                        ? (Math.Sign(f_Y) > 0
                            ? 0
                            : Consts.pi)
                        : Math.Atan2(f_Z, f_Y));
            }
        }

        /// <summary>Азимутальный угол</summary>
        [XmlIgnore]
        public double Phi
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return AngleXOY; }
            //[System.Diagnostics.DebuggerStepThrough]
            set { SetValue(R, new SpaceAngle(Thetta, value)); }
        }
        /// <summary>Угол места</summary>
        [XmlIgnore]
        public double Thetta
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return Math.Atan2(f_Z, R_XOY); }
            //[System.Diagnostics.DebuggerStepThrough]
            set { SetValue(R, new SpaceAngle(value, Phi)); }
        }

        /// <summary>Пространственный угол</summary>
        [XmlIgnore]
        public SpaceAngle Angle
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return new SpaceAngle(Thetta, Phi); }
            //[System.Diagnostics.DebuggerStepThrough]
            set { SetValue(R, value); }
        }

        /// <summary>Двумерный вектор - проекция в плоскости XOY</summary>
        public Vector2D VectorXOY
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return new Vector2D(f_X, f_Y); }
        }

        /// <summary>Двумерный вектор - проекция в плоскости XOZ (X->X; Z->Y)</summary>
        public Vector2D VectorXOZ
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return new Vector2D(f_X, f_Z); }
        }

        /// <summary>Двумерный вектор - проекция в плоскости YOZ (Y->X; Z->Y)</summary>
        public Vector2D VectorYOZ
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return new Vector2D(f_Y, f_Z); }
        }

        /// <summary>Длина в плоскости XOY</summary>
        public double R_XOY
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return Math.Sqrt(f_X * f_X + f_Y * f_Y); }
        }

        /// <summary>Длина в плоскости XOZ</summary>
        public double R_XOZ
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return Math.Sqrt(f_X * f_X + f_Z * f_Z); }
        }

        /// <summary>Длина в плоскости YOZ</summary>
        public double R_YOZ
        {
            //[System.Diagnostics.DebuggerStepThrough]
            get { return Math.Sqrt(f_Y * f_Y + f_Z * f_Z); }
        }

        /* -------------------------------------------------------------------------------------------- */

        [System.Diagnostics.DebuggerStepThrough]
        public Vector3D(double X) { f_X = X; f_Y = 0; f_Z = 0; }


        [System.Diagnostics.DebuggerStepThrough]
        public Vector3D(double X, double Y) { f_X = X; f_Y = Y; f_Z = 0; }

        [System.Diagnostics.DebuggerStepThrough]
        public Vector3D(double X, double Y, double Z) { f_X = X; f_Y = Y; f_Z = Z; }

        //[System.Diagnostics.DebuggerStepThrough]
        public Vector3D(SpaceAngle Angle) : this(1, Angle) { }


        //[System.Diagnostics.DebuggerStepThrough]
        public Vector3D(double R, SpaceAngle Angle)
        {
            if(Angle.AngleType == AngleType.Deg) Angle = Angle.InRad;
            var thetta = Angle.Thetta;
            var phi = Angle.Phi;

            f_Z = R * Math.Sin(thetta);
            var r = R * Math.Cos(thetta);
            f_X = r * Math.Cos(phi);
            f_Y = r * Math.Sin(phi);
        }
        private Vector3D(Vector3D V) : this(V.f_X, V.f_Y, V.f_Z) { }

        //[System.Diagnostics.DebuggerStepThrough]
        public Vector3D InBasis(Basis3D b)
        {
            return new Vector3D(
                b.xx * f_X + b.xy * f_Y + b.xz * f_Z,
                b.yx * f_X + b.yy * f_Y + b.yz * f_Z,
                b.zx * f_X + b.zy * f_Y + b.zz * f_Z);
        }

        public void Inc(double dx, double dy, double dz)
        {
            f_X += dx;
            f_Y += dy;
            f_Z += dz;
        }

        public void Inc_X(double dx) { f_X += dx; }
        public void Inc_Y(double dy) { f_Y += dy; }
        public void Inc_Z(double dz) { f_Z += dz; }

        public void Dec(double dx, double dy, double dz)
        {
            f_X -= dx;
            f_Y -= dy;
            f_Z -= dz;
        }

        public void Dec_X(double dx) { f_X -= dx; }
        public void Dec_Y(double dy) { f_Y -= dy; }
        public void Dec_Z(double dz) { f_Z -= dz; }

        /* -------------------------------------------------------------------------------------------- */

        [System.Diagnostics.DebuggerStepThrough]
        public override string ToString() { return string.Format("({0};{1};{2})", f_X, f_Y, f_Z); }

        [System.Diagnostics.DebuggerStepThrough]
        public override int GetHashCode()
        {
            unchecked
            {
                var result = f_X.GetHashCode();
                result = (result * 397) ^ f_Y.GetHashCode();
                result = (result * 397) ^ f_Z.GetHashCode();
                return result;
            }
        }

        /// <summary>Создает новый объект, который является копией текущего экземпляра.</summary>
        /// <returns>Новый объект, являющийся копией этого экземпляра.</returns><filterpriority>2</filterpriority>
        [System.Diagnostics.DebuggerStepThrough]
        object ICloneable.Clone() { return Clone(); }

        //[System.Diagnostics.DebuggerStepThrough]
        public Vector3D Clone() { return new Vector3D(this); }

        //[System.Diagnostics.DebuggerStepThrough]
        public override bool Equals(object obj) { return obj is Vector3D && Equals((Vector3D)obj); }

        [System.Diagnostics.DebuggerStepThrough]
        public string ToString(string Format)
        {
            return string.Format("({0};{1};{2})",
                f_X.ToString(Format), f_Y.ToString(Format), f_Z.ToString(Format));
        }

        /* -------------------------------------------------------------------------------------------- */

        #region IEquatable<Vector3D> Members

        //[System.Diagnostics.DebuggerStepThrough]
        public bool Equals(Vector3D other)
        {
            return Math.Abs(other.f_X - f_X) < double.Epsilon
                && Math.Abs(other.f_Y - f_Y) < double.Epsilon
                && Math.Abs(other.f_Z - f_Z) < double.Epsilon;
        }

        #endregion

        /* -------------------------------------------------------------------------------------------- */
    }
}
